package com.wedroid.r2d2.contact;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.wedroid.r2d2.contact.handler.CompositeContactHandler;
import com.wedroid.r2d2.http.RequestManager;
import com.wedroid.r2d2.support.ResponseResult;
import com.wedroid.r2d2.support.response.ContactResponse;
import com.wedroid.r2d2.support.response.GroupResponse;

/**
 * @author lute
 */
@Component
public class ContactManager {

    private static final Logger logger = LoggerFactory.getLogger(ContactManager.class);

    @Autowired
    private RequestManager requestManager;

    @Autowired
    private ContactStore contactStore;

    @Autowired
    private CompositeContactHandler contactHandler;

    private Contact droid;

    /**
     * @param droid
     * @param contacts
     */
    public void intialize(Contact droid, List<Contact> contacts) {
        this.droid = droid;

        // Handle droid self separately
        contactStore.storeFriend(droid);
        contactHandler.handleContacts(droid.getNickName(), Collections.singletonList(droid));

        handleContact(Collections.unmodifiableList(contacts));
    }

    /**
     * Refresh contacts by requesting contacts and classify them
     *
     * @return
     */
    public boolean requestContacts() {
        ResponseResult<ContactResponse> result = requestManager.getContacts();
        if (!result.isValid()) {
            logger.error("#requestContacts - Requesting contacts encounters error: [{}].", result.getMessage());
            return false;
        }

        ContactResponse contactResponse = result.getValue();
        handleContact(contactResponse.getContacts());

        // Request group members
        List<Contact> groups = contactStore.getAllGroups();
        boolean fetchingGroupMembers = requestGroupMembers(groups);
        if (!fetchingGroupMembers) {
            logger.error("#requestContacts - Requesting group members fails...");
            return false;
        }

        return true;
    }

    /**
     * Handle contact modification when message sync returns contact modification list
     *
     * @param contacts
     */
    public void handleContactModification(List<Contact> contacts) {
        // Handle non-group contact normally
        handleContact(
            contacts.stream()
                    .filter(contact -> !contact.isGroup())
                    .collect(Collectors.toList())
        );

        // Handle group contact separately for group member update
        requestGroupMembers(
            contacts.stream()
                    .filter(Contact::isGroup)
                    .collect(Collectors.toList())
        );
    }

    /**
     * Request group members and classify them
     * @param groups
     * @return
     */
    private boolean requestGroupMembers(List<Contact> groups) {
        ResponseResult<GroupResponse> result = requestManager.getGroupMember(groups);
        if (result.isValid()) {

            Map<Contact, List<Contact>> groupMembers = result.getValue().getGroupMembers();

            // handle contact first
            List<Contact> contacts = new ArrayList<>(groupMembers.keySet());
            handleContact(contacts);

            handleGroupMembers(groupMembers);

            return true;
        } else {
            if (StringUtils.isNoneBlank(result.getMessage())) {
                logger.error("#requestGroupMembers - Requesting group members encounters error: [{}].", result.getMessage());
            }
            return false;
        }
    }

    /**
     * Process general tasks on all contacts
     *
     * @param contacts
     */
    private void handleContact(final List<Contact> contacts) {
        classifyContacts(contacts);
        contactHandler.handleContacts(droid.getNickName(), contacts);
    }

    /**
     * @param groupMembers
     */
    private void handleGroupMembers(final Map<Contact, List<Contact>> groupMembers) {
        groupMembers.entrySet()
            .parallelStream()
            .forEach(entry -> {
                contactStore.storeGroup(ensureGroupEncryChatRoomId(entry.getKey()));
                entry.getValue()
                    .parallelStream()
                    .forEach(member -> contactStore.storeGroupMember(entry.getKey().getUserName(), member));
            });
        contactHandler.handleGroupMembers(droid.getNickName(), groupMembers);
    }

    /**
     * Classify contacts to different types
     * @param contacts
     */
    private void classifyContacts(List<Contact> contacts) {
        for (Contact contact : contacts) {
            if (contact.isOfficial()) {
                contactStore.storeOfficial(contact);
            } else if (contact.isSpecial()) {
                contactStore.storeSpecial(contact);
            } else if (contact.isGroup()) {
                contactStore.storeGroup(ensureGroupEncryChatRoomId(contact));
            } else {
                contactStore.storeFriend(contact);
            }
        }
    }

    /**
     * @param group
     * @return
     */
    private Contact ensureGroupEncryChatRoomId(Contact group) {
        if (group.isGroup()) {
            // Check group's encryChatRoomId
            if (StringUtils.isBlank(group.getEncryChatRoomId())) {
                // Try to find encryChatRoomId from old store
                Contact storedGroup = contactStore.getContact(group.getUserName());
                if (storedGroup != null && StringUtils.isNotBlank(storedGroup.getEncryChatRoomId())) {
                    group.setEncryChatRoomId(storedGroup.getEncryChatRoomId());
                }
            }
        }
        return group;
    }

    /**
     * @return the droid
     */
    public Contact getDroid() {
        return droid;
    }

    /**
     * Get contact by user name
     *
     * @param username
     * @return
     */
    public Contact getContact(String username) {
        Contact contact = contactStore.getContact(username);
        if (contact == null) {
            contact = new Contact();
            contact.setUserName(username);

            if (username.startsWith("@@")) {
                // If the desired contact is a group and it does not exist in cache,
                // then it is a unsaved group. So pull out the group members in this
                // unsaved group.
                requestGroupMembers(Collections.singletonList(contact));
                return contactStore.getContact(username);
            } else if (contact.isSpecial()) {
                // If the desired contact is a special contact and not in cache,
                // then save it in cache.
                handleContact(Collections.singletonList(contact));
                return contactStore.getContact(username);
            }
        }
        return contact;
    }

    /**
     * Get contact preferred name
     * @param contact
     * @return
     */
    public String getContactPreferredName(Contact contact) {
        if (contact != null) {
            return contact.getPreferredName();
        } else {
            return null;
        }
    }

    /**
     * @param username
     * @return
     */
    public boolean isSelf(String username) {
        return droid != null && StringUtils.equals(droid.getUserName(), username);
    }

    /**
     * @param groupName
     * @param username
     * @return
     */
    public Contact getGroupMember(String groupName, String username) {
        Contact member = contactStore.getGroupMember(groupName, username);
        if (member == null) {
            Contact group = getContact(groupName);
            if (group != null) {
                return contactStore.getGroupMember(groupName, username);
            }
        }
        return member;
    }

}
